#include "NESDisplayGL.h"
#include <QMessageBox>




const GLfloat NESDisplayGL::DefaultVertex[]={
  -1.0f,-1.0f,0.0f, 0.0f, 0.0f,
   1.0f,-1.0f,0.0f, 1.0f, 0.0f,
   1.0f, 1.0f,0.0f, 1.0f, 1.0f,
  -1.0f, 1.0f,0.0f, 0.0f, 1.0f,
  -1.0f,-1.0f,0.0f, 1.0f, 0.0f,
   1.0f,-1.0f,0.0f, 0.0f, 0.0f,
   1.0f, 1.0f,0.0f, 0.0f, 1.0f,
  -1.0f, 1.0f,0.0f, 1.0f, 1.0f,
};

const GLuint NESDisplayGL::DefaultIndices[]={
  0,1,2,3,7,6,5,4
};




void PrintfQMatrix4x4(QMatrix4x4& M){
  const float* m;

  m=M.constData();
  for(int i=0;i<4;i+=1){
    qDebug()<<m[i]<<m[i+4]<<m[i+8]<<m[i+12];
  }
}




NESDisplayGL::NESDisplayGL(QWidget* parent):QOpenGLWidget(parent){
  int i;

  RightKeyMenu=new QMenu("选项",parent);
  action_Reset=RightKeyMenu->addAction("复位视角");
  connect(action_Reset,&QAction::triggered,this,&NESDisplayGL::action_Reset_Click);

  mouseRightPress_X=0;
  mouseRightPress_Y=0;
  mouseMove_X=0;
  mouseMove_Y=0;

  InitDone=false;
  IsDisplay=false;
  IsMovability=false;
  OpenGLVersion=QString();

  FrameWidth=256;
  FrameHeight=240;

  VertexSize=sizeof(GLfloat)*(3+2)*8;
  Vertex=new GLfloat[VertexSize/sizeof(VertexSize)];
  
  IndicesSize=sizeof(GLuint)*8;
  Indices=new GLuint[IndicesSize/sizeof(GLuint)];

  NES_Texture_SrcBuffer=new uint32_t[256*240];
  for(i=0;i<256*240;i++){
    NES_Texture_SrcBuffer[i]=0xFF000000U;
  }

  QImage image(":/image/0x9DEFA478_icon_black_2160x.png");
  QImage logoSrc=image.convertToFormat(QImage::Format::Format_RGB32);
  int Width;
  int Height;
  Width=logoSrc.size().width();
  Height=logoSrc.size().height();

  QImage logo;
  if((Width<1024)||(Height<1024)){
    if(Width>Height){
      logo=logoSrc.copy((Width-Height)/2,0,Height,Height);
      Width=Height;
    }else{
      logo=logoSrc.copy(0,(Height-Width)/2,Width,Width);
      Height=Width;
    }
  }else{
    logo=logoSrc.copy((Width-1024)/2,(Height-1024)/2,1024,1024);
    Width=1024;
    Height=1024;
  }
  Logo_Texture_SrcBuffer=new uint32_t[Width*Height];
  memcpy(Logo_Texture_SrcBuffer,logo.bits(),Width*Height*sizeof(uint32_t));

}

NESDisplayGL::~NESDisplayGL(){

  delete[] Logo_Texture_SrcBuffer;
  delete[] NES_Texture_SrcBuffer;

  delete[] Indices;
  delete[] Vertex;
}

void NESDisplayGL::InputFrame(uint32_t* ColorArray){
  if(IsDisplay){
    memcpy(NES_Texture_SrcBuffer,ColorArray,256*240*sizeof(uint32_t));
    update();
  }
}

void NESDisplayGL::SetIsDisplay(bool IsDisplay){
  int i;

  this->IsDisplay=IsDisplay;
  if(!IsDisplay){
    for(i=0;i<256*240;i++){
      NES_Texture_SrcBuffer[i]=0xFF000000U;
    }
    update();
  }
}

QString NESDisplayGL::GetOpenGLVersion(){
  return OpenGLVersion;
}

void NESDisplayGL::mousePressEvent(QMouseEvent* event){
  int locX;
  int locY;

  locX=(int)event->position().x();
  locY=(int)event->position().y();

  switch(event->button()){
    case Qt::MouseButton::RightButton:
      mouseRightPress_X=locX;
      mouseRightPress_Y=locY;
      mouseMove_X=locX;
      mouseMove_Y=locY;
      break;
    default:
      break;
  }

}

void NESDisplayGL::mouseReleaseEvent(QMouseEvent* event){
  int locX;
  int locY;

  locX=(int)event->position().x();
  locY=(int)event->position().y();

  switch(event->button()){
    case Qt::MouseButton::RightButton:
      if((mouseRightPress_X==locX)&&(mouseRightPress_Y==locY)){
        //ClickEvent

        RightKeyMenu->exec(QPoint((int)event->globalPosition().x(),(int)event->globalPosition().y()));
      }
      break;
    case Qt::MouseButton::MiddleButton:
      M_View_Reset();
      M_Model_R_Reset();
      update();
      break;
    default:
      break;
  }
  
}

void NESDisplayGL::mouseMoveEvent(QMouseEvent* event){
  Qt::MouseButtons Buttons;
  int locX;
  int locY;

  Buttons=event->buttons();

  locX=(int)event->position().x();
  locY=(int)event->position().y();

  if(Buttons&Qt::MouseButton::RightButton){
    
    int dX;
    int dY;
    int dl_dl;
    float dl;

    dX=locX-mouseMove_X;
    dY=locY-mouseMove_Y;

    dl_dl=dX*dX+dY*dY;
    dl=qSqrt<int>(dl_dl);

    QMatrix4x4 mat;

    mat.setToIdentity();

    mat.rotate(dl*0.67f,dY,dX,0.0f);
    M_Model_R=mat*M_Model_R;
    M_Model_R_IsUpdate=true;
    update();

    mouseMove_X=locX;
    mouseMove_Y=locY;
  }

}

void NESDisplayGL::wheelEvent(QWheelEvent* event){
  
  int v=event->angleDelta().y();

  float angle=v/8.0f;
  float scale=angle/180+1.0f;


  M_View.scale(scale,scale,scale);
  M_View_IsUpdate=true;

  update();

}

void NESDisplayGL::action_Reset_Click(){
  M_View_Reset();
  M_Model_L_Reset();
  M_Model_R_Reset();
  update();
}

void NESDisplayGL::initializeGL(){

  InitDone=initializeOpenGLFunctions();

  if(!InitDone){
    QMetaObject::invokeMethod(this,"InitError",Qt::ConnectionType::QueuedConnection);
    return;
  }

  GLint val;
  glGetIntegerv(GL_MAX_VERTEX_ATTRIBS,&val);
  if(val<4){
    InitDone=false;
    QMetaObject::invokeMethod(this,"Error",Qt::ConnectionType::QueuedConnection,Q_ARG(QString,QString("硬件支持的顶点数过少")));
  }

  const GLubyte* version=glGetString(GL_VERSION);
  OpenGLVersion=QString((char*)version);




  try{
    if(!shaderProgram.addCacheableShaderFromSourceFile(QOpenGLShader::Vertex,":/shader/NESDisplayGL.vert")){
      throw "NESDisplayGL.vert:\r\n  "+shaderProgram.log();
    }
    if(!shaderProgram.addCacheableShaderFromSourceFile(QOpenGLShader::Fragment,":/shader/NESDisplayGL.frag")){
      throw "NESDisplayGL.frag:\r\n  "+shaderProgram.log();
    }
    if(!shaderProgram.link()){
      throw "Link:\r\n  "+shaderProgram.log();
    }
  }catch(QString e){
    InitDone=false;
    QMetaObject::invokeMethod(this,"Error",Qt::ConnectionType::QueuedConnection,Q_ARG(QString,"着色器编译错误:"+e));
    return;
  }




  memcpy(Vertex,DefaultVertex,VertexSize);
  memcpy(Indices,DefaultIndices,IndicesSize);
  Vertex[5*2+3+1]=240.0f/256.0f;
  Vertex[5*3+3+1]=240.0f/256.0f;


  

  glGenVertexArrays(1,&VAO);
  glGenBuffers(1,&VBO);

  glBindVertexArray(VAO);

  glBindBuffer(GL_ARRAY_BUFFER,VBO);
  glBufferData(GL_ARRAY_BUFFER,VertexSize,Vertex,GL_STATIC_DRAW);
  glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,5*sizeof(GLfloat),(void*)0);
  glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,5*sizeof(GLfloat),(void*)(3*sizeof(GLfloat)));
  glEnableVertexAttribArray(0);
  glEnableVertexAttribArray(1);

  glGenBuffers(1,&EBO);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER,IndicesSize,Indices,GL_STATIC_DRAW);

  glBindBuffer(GL_ARRAY_BUFFER,0);
  glBindVertexArray(0);

  
  glGenTextures(1,&NES_Texture);
  glBindTexture(GL_TEXTURE_2D,NES_Texture);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);

  glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,256,256,0,GL_BGRA,GL_UNSIGNED_BYTE,nullptr);
  
  glBindTexture(GL_TEXTURE_2D,0);


  glGenTextures(1,&Logo_Texture);
  glBindTexture(GL_TEXTURE_2D,Logo_Texture);

  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);

  glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,1024,1024,0,GL_BGRA,GL_UNSIGNED_BYTE,Logo_Texture_SrcBuffer);
  
  glBindTexture(GL_TEXTURE_2D,0);

  

  shaderProgram.bind();

  QMatrix4x4 Matrix4x4;

  Matrix4x4.setToIdentity();
  Matrix4x4.ortho(-1.0f,1.0f,-1.0f,1.0f,1.0f,-1.0f);
  // Matrix4x4.perspective(45.0f,1.0f,0.1f,100.0f);
  qDebug()<<"M_Projection";
  PrintfQMatrix4x4(Matrix4x4);
  shaderProgram.setUniformValue("M_Projection",Matrix4x4);
  
  M_View_Reset();
  M_Model_L_Reset();
  M_Model_R_Reset();
  shaderProgram.setUniformValue("M_View",M_View);
  shaderProgram.setUniformValue("M_Model_L",M_Model_L);
  shaderProgram.setUniformValue("M_Model_R",M_Model_R);
  M_View_IsUpdate=false;
  M_Model_L_IsUpdate=false;
  M_Model_R_IsUpdate=false;



  
}

void NESDisplayGL::resizeGL(int w, int h){

  if(!InitDone){
    return;
  }
  
  FrameWidth=w;
  FrameHeight=h;

}

void NESDisplayGL::paintGL(){

  if(!InitDone){
    return;
  }


  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  glFrontFace(GL_CCW);

  glEnable(GL_DEPTH_TEST);

  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glClearColor(0.0f,0.0f,0.0f,0.0f);


  shaderProgram.bind();

  if(M_View_IsUpdate){
    shaderProgram.setUniformValue("M_View",M_View);
    M_View_IsUpdate=false;
  }

  glBindVertexArray(VAO);

  if(M_Model_L_IsUpdate){
    shaderProgram.setUniformValue("M_Model_L",M_Model_L);
  }
  if(M_Model_R_IsUpdate){
    shaderProgram.setUniformValue("M_Model_R",M_Model_R);
  }
  shaderProgram.setUniformValue("NES_Texture",(GLint)0);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D,NES_Texture);
  glTexSubImage2D(GL_TEXTURE_2D,0,0,256-240,256,240,GL_BGRA,GL_UNSIGNED_BYTE,NES_Texture_SrcBuffer);
  glDrawElements(GL_QUADS,4,GL_UNSIGNED_INT,(void*)0);
  glBindTexture(GL_TEXTURE_2D,Logo_Texture);
  glDrawElements(GL_QUADS,4,GL_UNSIGNED_INT,(void*)(4*sizeof(GLuint)));
  
  glBindVertexArray(0);

}

void NESDisplayGL::M_View_Reset(){
  M_View.setToIdentity();
  M_View.translate(0.0f,0.0f,0.0f);
  M_View_IsUpdate=true;
}

void NESDisplayGL::M_Model_L_Reset(){
  M_Model_L.setToIdentity();
  M_Model_L.translate(0.0f,0.0f,0.0f);
  M_Model_L_IsUpdate=true;
}

void NESDisplayGL::M_Model_R_Reset(){
  M_Model_R.setToIdentity();
  M_Model_R.rotate(0.0f,0.0f,0.0f,1.0f);
  M_Model_R_IsUpdate=true;
}

void NESDisplayGL::InitError(){
  QMessageBox::critical((QWidget*)this->parent(),"错误","OpenGL初始化失败");
}

void NESDisplayGL::Error(QString s){
  QMessageBox::critical((QWidget*)this->parent(),"错误",s);
}



